// exception.cc 
//	Entry point into the Nachos kernel from user programs.
//	There are two kinds of things that can cause control to
//	transfer back to here from user code:
//
//	syscall -- The user code explicitly requests to call a procedure
//	in the Nachos kernel.  Right now, the only function we support is
//	"Halt".
//
//	exceptions -- The user code does something that the CPU can't handle.
//	For instance, accessing memory that doesn't exist, arithmetic errors,
//	etc.  
//
//	Interrupts (which can also cause control to transfer from user
//	code into the Nachos kernel) are handled elsewhere.
//
// For now, this only handles the Halt() system call.
// Everything else core dumps.
//
// Copyright (c) 1992-1993 The Regents of the University of California.
// All rights reserved.  See copyright.h for copyright notice and limitation 
// of liability and disclaimer of warranty provisions.

#include "copyright.h"
#include "system.h"
#include "syscall.h"
#include "filesys.h"
#include "bitmap.h"
#include "addrspace.h"

extern void StartProcess(char *file);
extern void StartProcess(int arg);
//----------------------------------------------------------------------
// Excep_SC_Exec  
//  创建一个内核线程，将新的用户线程映射到内核线程上
//----------------------------------------------------------------------

void Excep_SC_Exec(){
     printf("Execute system call of Exec()\n");
    // 读入参数
    // 参数的地址保留在 $4 寄存器中
   
    int addr = machine->ReadRegister(4);
    char filename[50];
    // 到内存中，读出可执行文件名
    int i =0;
    do{
        //read filename from mainMemory
        machine->ReadMem(addr+i,1,(int *)&filename[i]);
    }while(filename[i++]!='\0');

    // 读入可执行程序，并分配内存
    OpenFile *executable = fileSystem->Open(filename);
    AddrSpace *space;

    if (executable == NULL) {
	printf("Unable to open file %s\n", filename);
	return;
    }
    space = new AddrSpace(executable); 
    space->Print(); // 输出该作业的页表信息
    delete executable;			// close file

   
    

    // 创建内核线程
    Thread *t = new Thread("forked thread");
    // 将新创建的用户线和内核线程映射起来
    t->space = space; 
    // 重载 StartPorcess 进行初始化寄存器并执行可执行程序
    printf("exec Fork\n");
    t->Fork(StartProcess, space->getSpaceID());

    //return spaceID
    // 寄存器2 用于放函数的返回值
    machine->WriteRegister(2,space->getSpaceID());
}

//----------------------------------------------------------------------
// Excep_SC_Exit
//  结束一个用户进程
// 调用 用户线程的析构函数，内核线程与用户线程分离，内核线程接着向下执行直到退出
//----------------------------------------------------------------------

void Excep_SC_Exit(){
    // 撤销用户线程
    delete currentThread->space;
    // 直接将当前的内核线程执行终止
     currentThread->Finish();
}


//----------------------------------------------------------------------
// Excep_SC_Join
//  等待一个用户进程执行结束
// 采用信号量 P V 操作执行
//----------------------------------------------------------------------

void Execp_SC_Join(){
    // 将要等待的进程pid读出
    printf("Execute system call of Join\n");
    int pid = machine->ReadRegister(4);
    printf("current thread wait pid: %d\n",pid);

    // 执行信号量的P 操作
    currentThread->space->wait(pid);

}

//----------------------------------------------------------------------
// Excep_SC_Yield 
// 将当前用户进程，从运行态，强行改为就绪态
//----------------------------------------------------------------------

void Execp_SC_Yield(){
    // 当前线程的上下文切换，还是依赖内核线程的切换
    currentThread->Yield();
}


//----------------------------------------------------------------------
// Excep_SC_Create 
// 在nachos 文件系统中 创建文件
//----------------------------------------------------------------------

void Execp_SC_Create(){
    // 从寄存器中读入参数
    int base = machine->ReadRegister(4);
    int value;
    int count=0;
    char *FileName= new char[128];
    do{
        machine->ReadMem(base+count,1,&value);
        FileName[count]=*(char*)&value;
        count++;
    } while(*(char*)&value!='\0'&&count<128);

    if(!fileSystem->Create(FileName,0)) //call Create() in FILESYS,see filesys.h
        printf("create file %s failed!\n",FileName);
    else
        DEBUG('f',"create file %s succeed!\n",FileName);

}


//----------------------------------------------------------------------
// Excep_SC_Open
// 打开一个
//----------------------------------------------------------------------

void Execp_SC_Open(){
    int base = machine->ReadRegister(4);
    int value;
    int count=0;
    char *FileName= new char[128];
    do{
        machine->ReadMem(base+count,1,&value);
        FileName[count]=*(char*)&value;
        count++;
    } while(*(char*)&value!='\0'&&count<128);

    int fileid;
    // 打开文件，返回打开文件的指针
    OpenFile* openfile = fileSystem->Open(FileName);

    printf("Open filename %s .\n",FileName);
    if(openfile == nullptr){
        //文件不存在
        printf("File %s not Exites,could not open it .\n",FileName);
        fileid = -1;
    }else {
        // 文件存在，将其加入进程的带开文件表
        fileid = currentThread->space->getFileDescriptor(openfile);
        // 打开文件表已满
        if(fileid < 0)
            printf("Too many files opened!\n");
        else
            DEBUG('f',"file :%s open secceed! the file id is %d\n",FileName,fileid);

    }


    // 通过寄存器2 返回值
    machine->WriteRegister(2,fileid);
 }

//----------------------------------------------------------------------
// Execp_SC_Write()
// 向 fid 对应的文件中
//----------------------------------------------------------------------

 void Execp_SC_Write(){

    //Write(char *buffer, int size, OpenFileId id);
     // 从4 5 6号寄存器 读出参数
     int base = machine->ReadRegister(4);
     int size = machine->ReadRegister(5);
     int fid = machine->ReadRegister(6);
     printf("base=%d, size=%d, fileId=%d \n",base,size,fid);

    int value;
    int count=0;
    OpenFile* openfile;
    // 目前一次性输入的字符串大小限制在 128    
    char* buffer= new char[128];
    do{
        machine->ReadMem(base+count,1,&value);
        buffer[count] = *(char*)&value;
        count++;
    } while((*(char*)&value!='\0') && (count<size));
    buffer[size]='\0';
    
    // 不能向标准输出中写, fid要满足约束 
    ASSERT(fid >= 1 && fid <10);
    openfile = currentThread->space->getOpenFile(fid);
     // 取出打开文件指针
     if(fid == 1 || fid == 2){
         char * filename;
            if(fid == 1){
                 filename = "stdOut\0";  // 要有 '\0'
            }else if(fid == 2){
                 filename = "stdErr\0";
            }
         if(openfile == nullptr){
         // 向屏幕输出，向stdOut stdErr 文件中写入，因为在打开文件表中，0 1 2 初始是空
         // 向磁盘中获取输入输出文件
            openfile = fileSystem->Open(filename);
            DEBUG('a',"SC_Write open fid %d  filename %s in disk!\n",fid,filename);

         }
         if(openfile == nullptr){
             // 从磁盘中没有读到 则自行创建该文件
            if(!fileSystem->Create(filename,0)){
                printf("create file %s failed!\n",filename);
                return;
            }
            openfile = fileSystem->Open(filename);
            DEBUG('a',"SC_Write create fid %d  filename %s in disk!\n",fid,filename);
         }

         // 将标准输入输出 加入打开文件表
         currentThread->space->fileRedirect(fid, openfile);
     }
    if(openfile == nullptr){
        // 打开文件失败
        //DEBUG('a',"SC_Write open fid %d failed!.\n",fid);
        printf("SC_Write open fid %d.\n",fid);
        return;
    }

    int WritePosition = openfile->Length();
    openfile->Seek(WritePosition); //append write
    printf("write position %d \n.",WritePosition);
    // 开始执行写入
    printf("SC_Write wirte fid %d success!\n",fid);
    if(openfile->Write(buffer,size) == size)
        DEBUG('a',"SC_Write wirte fid %d success!\n",fid);

    
 }


// 用来测试
 void testWrite(){
     OpenFile *openFile;
     char * to = "writeTo\0";
     if ( (openFile = fileSystem->Open(to)) == NULL)
    {
        // 如果文件不存在
	// file "to" does not exits, then create one
	    if (!fileSystem->Create(to, 0)) 
	    {
	        printf("Append: couldn't create the file %s to append\n", to);
	        return;
	    }
    	openFile = fileSystem->Open(to);
    }

    ASSERT(openFile != NULL);
    // append from position "start"
    int start = openFile->Length();
    //定位文件指针
    openFile->Seek(start);
    printf("Append:write the nachos at %d \n", start);
// Append the data in TransferSize chunks
  
    char*buffer = new char[128];
    int result;
    int amount = 20;
    for(int i =0;i < amount;i++)
        buffer[i] = 't';
	printf("start value: %d,  amountRead %d, ", start, amount);
//	result = openFile->WriteAt(buffer, amountRead, start);
	result = openFile->Write(buffer, amount);
	printf("result of write: %d\n", result);
	ASSERT(result == amount);

 }
//----------------------------------------------------------------------
// ExceptionHandler
// 	Entry point into the Nachos kernel.  Called when a user program
//	is executing, and either does a syscall, or generates an addressing
//	or arithmetic exception.
//
// 	For system calls, the following is the calling convention:
//
// 	system call code -- r2
//		arg1 -- r4
//		arg2 -- r5
//		arg3 -- r6
//		arg4 -- r7
//
//	The result of the system call, if any, must be put back into r2. 
//  注意：这里说了，系统调用的返回值一定要放在 r2 寄存器中
// And don't forget to increment the pc before returning. (Or else you'll
// loop making the same system call forever!
//
//	"which" is the kind of exception.  The list of possible exceptions 
//	are in machine.h.
//----------------------------------------------------------------------

void
ExceptionHandler(ExceptionType which)
{
    // 从2号寄存器中读出系统调用号
    int type = machine->ReadRegister(2);

    if ( which == SyscallException ) {
        switch (type)
        {
        case SC_Halt:
            DEBUG('a', "Shutdown, initiated by user program.\n");
   	        interrupt->Halt();
            break;
        case SC_Exec:
            DEBUG('a',"Exec a new user program.\n"); 
            Excep_SC_Exec();
            break;
        case SC_Exit:
            // 当用户程序执行结束 或者执行系统调用Exit 都会跳转到这里
            DEBUG('a',"Exit a user program.\n"); 
            Excep_SC_Exit();
            break;
        case SC_Join:
            DEBUG('a',"Join, waitng for a user program finish.\n"); 
            Execp_SC_Join();
            break;
        case SC_Yield:
            DEBUG('a',"Yield, turn current thread from running to ready.\n");
            Execp_SC_Yield(); 
            break;
        case SC_Create:
            DEBUG('a',"Create, create a file in nachos file system.\n");
            Execp_SC_Create();
            break;
        case SC_Open:
            DEBUG('a',"Open, open a nachos file in nachos file system and return the file id in the system file table .\n");
            // 打开文件，返回打开文件的id
            Execp_SC_Open();
            break;
        case SC_Write:
            DEBUG('a',"Wirte, wirte into a nachos file. \n");
            //Execp_SC_Write();
            testWrite();
        break;
        default:

             printf("unfinished sc %d\n",type);
            break;
        }
	
    } else {
	printf("Unexpected user mode exception %d %d\n", which, type);
	ASSERT(FALSE);
    }
}
